#ifndef B1934FA4_BD73_4ed9_BC32_F234AFAB0901_CLIENT_HPP
#define B1934FA4_BD73_4ed9_BC32_F234AFAB0901_CLIENT_HPP

#include <boost/asio/detail/mutex.hpp>
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>

#include "netconnection.hpp"
#include "netconnectionpool.hpp"
namespace qserv 
{
    /// The top-level class of the client.
    template<typename Work_Handler, typename Work_Allocator, typename Socket_Service = boost::asio::ip::tcp::socket>
    class client
        : private boost::noncopyable
    {
    public:
        /// The type of the NetConnection.
        typedef NetConnection<Work_Handler, Socket_Service> connecion_type;
        typedef boost::shared_ptr< connecion_type > connecion_ptr;

        /// The type of the connection_pool.
        typedef NetConnPool<Work_Handler, Work_Allocator, Socket_Service> connection_pool_type;
        typedef boost::shared_ptr< connection_pool_type > connection_pool_ptr;

        /// Construct the client object for connect to specified TCP address and port.
        client(const std::string& address,
            unsigned short port,
            connection_pool_ptr connection_pool)
            : m_mutex(),
            m_pConnectionPool(connection_pool),
            m_Endpoint(boost::asio::ip::address::from_string(address), port)
        {
            BOOST_ASSERT(connection_pool != 0);
        }

        /// Construct the client object for connect to specified TCP address and port.
        client(connection_pool_type* service_handler_pool)
            : m_mutex(),
            m_pConnectionPool(service_handler_pool)
        {
            BOOST_ASSERT(service_handler_pool != 0);
        }

        /// Destruct the client object.
        ~client()
        {
            // Destroy service_handler pool.
            m_pConnectionPool.reset();
        }

        /// Make an connection with given io_service and work_service.
        void connect(boost::asio::io_service& io_service,
            boost::asio::io_service& work_service,
            boost::asio::ip::tcp::endpoint& endpoint)
        {
            // Get new handler for connect.
            connecion_ptr new_handler = m_pConnectionPool->getServConn(io_service,
                work_service,
                m_mutex);

            boost::function<void (connecion_ptr)> cmdConnectionCancel(boost::bind(&client::reconnect, this, _1,
                boost::ref(io_service), boost::ref(work_service)));
            new_handler->hookConnectionCancel(cmdConnectionCancel);
            // Use new handler to connect.
            new_handler->connect(endpoint);
        }

        /// Make an connection with the given parent_handler.
        template<typename Parent_Handler>
        void connect(Parent_Handler& parent_handler,
            boost::asio::ip::tcp::endpoint& endpoint)
        {
            // Get new handler for connect.
            connecion_ptr new_handler = m_pConnectionPool->getServConn(parent_handler.io_service(),
                parent_handler.work_service(),
                m_mutex);

            boost::function<void (connecion_ptr)> cmdConnectionCancel(boost::bind(&client::reconnect, this, _1, 
                boost::ref(parent_handler.io_service()), boost::ref(parent_handler.work_service())));
            new_handler->hookConnectionCancel(cmdConnectionCancel);
            // Execute in work_thread, because connect will be called in the same thread.
            parent_handler.set_child(new_handler.get());
            new_handler->set_parent(&parent_handler);

            // Use new handler to connect.
            new_handler->connect(endpoint);
        }

        /// Make an connection with given io_service and work_service.
        void connect(boost::asio::io_service& io_service,
            boost::asio::io_service& work_service)
        {
            // Connect with the internal endpoint.
            connect(io_service, work_service, m_Endpoint);
        }

        /// Make an connection to specific host with given io_service and work_service.
        void connect(boost::asio::io_service& io_service,
            boost::asio::io_service& work_service,
            const std::string& address,
            unsigned short port)
        {
            // Prepare endpoint for connect.
            boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(address), port);

            // Connect with the given endpoint.
            connect(io_service, work_service, endpoint);
        }

        /// Make an connection with the given parent_handler.
        template<typename Parent_Handler>
        void connect(Parent_Handler& parent_handler)
        {
            // Connect with the internal endpoint.
            connect(parent_handler, m_Endpoint);
        }

        /// Make an connection to specific host with the given parent_handler.
        template<typename Parent_Handler>
        void connect(Parent_Handler& parent_handler,
            const std::string& address,
            unsigned short port)
        {
            // Prepare endpoint for connect.
            boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(address), port);

            // Connect with the given endpoint.
            connect(parent_handler, endpoint);
        }

        void reconnect(connecion_ptr conn, boost::asio::io_service& io_service,
            boost::asio::io_service& work_service)
        {
            boost::function<void (connecion_ptr)> cmdConnectionCancel(boost::bind(&client::reconnect, this, _1,
                boost::ref(io_service), boost::ref(work_service)));
            // Use new handler to connect.
            std::cout << "Reconnect to " << m_Endpoint.address().to_string() << ":" << m_Endpoint.port() << std::endl;
            conn->hookConnectionCancel(cmdConnectionCancel);
            conn->reconnect(m_Endpoint);
        }

    private:
        /// Mutex to protect access to internal data.
        boost::asio::detail::mutex m_mutex;

        /// The pool of service_handler objects.
        connection_pool_ptr m_pConnectionPool;

        /// The server endpoint.
        boost::asio::ip::tcp::endpoint m_Endpoint;
    };

} // namespace qserv

#endif // B1934FA4_BD73_4ed9_BC32_F234AFAB0901_CLIENT_HPP
